package edu.hda.pt.utils.connections.mina.codec;

import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

import edu.hda.pt.client.Config;
import edu.hda.pt.utils.Stream;
import edu.hda.pt.utils.TestObject;
import edu.hda.pt.utils.Stream.StreamState;
import edu.hda.pt.utils.connections.mina.MinaConstants;

/**
 * This class decodes received messages
 * 
 * @author Marc Hammerton
 * 
 */
public class MessageDecoder extends CumulativeProtocolDecoder {

	/**
	 * State key for saving the type in the session
	 */
	private final String TYPE_STATE_KEY = "TYPE_STATE_KEY.STATE";

	/**
	 * State key for saving the body length in the session
	 */
	private final String BODYLENGTH_STATE_KEY = "BODYLENGTH_STATE_KEY.STATE";

	/**
	 * State key for saving if the short header is already read in the session
	 */
	private final String SHORT_HEADER_STATE_KEY = "SHORT_HEADER_STATE_KEY.STATE";

	/**
	 * State key for saving if the long header is already read in the session
	 */
	private final String LONG_HEADER_STATE_KEY = "LONG_HEADER_STATE_KEY.STATE";

	/**
	 * Decodes a received message. This method is called until the whole message
	 * is received. Then the type of the message is determined, the value read
	 * and forwarded to the next filter (e.g. the handler)
	 * 
	 * 
	 * @see org.apache.mina.filter.codec.ProtocolDecoder#decode(org.apache.mina.core
	 *      .session.IoSession, org.apache.mina.core.buffer.IoBuffer,
	 *      org.apache.mina.filter.codec.ProtocolDecoderOutput)
	 */
	@Override
	public boolean doDecode(IoSession session, IoBuffer in,
			ProtocolDecoderOutput out) throws Exception {

		short type = (Short) session.getAttribute(TYPE_STATE_KEY, (short) 0);
		int bodylength = (Integer) session
				.getAttribute(BODYLENGTH_STATE_KEY, 0);
		boolean readShortHeader = (Boolean) session.getAttribute(
				SHORT_HEADER_STATE_KEY, false);
		boolean readLongHeader = (Boolean) session.getAttribute(
				LONG_HEADER_STATE_KEY, false);

		if (in.remaining() < MinaConstants.HEADER_LENGTH_SHORT) {
			return false;
		}

		if (!readShortHeader) {
			type = in.getShort();
			session.setAttribute(TYPE_STATE_KEY, type);
			readShortHeader = true;
			session.setAttribute(SHORT_HEADER_STATE_KEY, readShortHeader);
		}

		if (type == Config.HANDLEBYTES || type == Config.HANDLEOBJECT
				|| type == Config.HANDLESTRING) {

			if (in.remaining() < MinaConstants.HEADER_LENGTH_LONG) {
				return false;
			}
			if (!readLongHeader) {
				bodylength = in.getInt();
				session.setAttribute(BODYLENGTH_STATE_KEY, bodylength);
				readLongHeader = true;
				session.setAttribute(LONG_HEADER_STATE_KEY, readLongHeader);
			}
		} else if (type == Config.HANDLEBOOL) {
			bodylength = 1;
			session.setAttribute(BODYLENGTH_STATE_KEY, bodylength);
		} else if (type == Config.HANDLEINTEGER) {
			bodylength = 4;
			session.setAttribute(BODYLENGTH_STATE_KEY, bodylength);
		} else if (type == Config.HANDLELONG) {
			bodylength = 8;
			session.setAttribute(BODYLENGTH_STATE_KEY, bodylength);
		} else if (type == Config.HANDLESTREAM_START) {
			bodylength = 4;
			session.setAttribute(BODYLENGTH_STATE_KEY, bodylength);
		} else if (type == Config.HANDLESTREAM_STARTED) {
			bodylength = 0;
			session.setAttribute(BODYLENGTH_STATE_KEY, bodylength);
		} else if (type == Config.HANDLESTREAM_STOP) {
			bodylength = 0;
			session.setAttribute(BODYLENGTH_STATE_KEY, bodylength);
		}

		if (in.remaining() < bodylength) {
			return false;
		}

		if (type == Config.HANDLEBOOL) {
			boolean value = in.get() == 1 ? true : false;
			out.write(value);
		} else if (type == Config.HANDLEBYTES) {
			byte[] value = new byte[bodylength];
			in.get(value);
			out.write(value);
		} else if (type == Config.HANDLEINTEGER) {
			int value = in.getInt();
			out.write(value);
		} else if (type == Config.HANDLELONG) {
			long value = in.getLong();
			out.write(value);
		} else if (type == Config.HANDLEOBJECT) {
			TestObject object = null;
			try {
				object = (TestObject) in.getObject();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			out.write(object);
		} else if (type == Config.HANDLESTRING) {
			String value = "";
			try {
				value = in.getString(bodylength, Charset.forName("UTF-8")
						.newDecoder());
			} catch (CharacterCodingException e) {
				e.printStackTrace();
			}
			out.write(value);
		} else if (type == Config.HANDLESTREAM_START) {
			int fs = in.getInt();

			Stream stream = new Stream(fs);
			stream.setState(StreamState.START);
			out.write(stream);
		} else if (type == Config.HANDLESTREAM_STARTED) {
			Stream stream = new Stream();
			stream.setState(StreamState.RUNNING);
			out.write(stream);
		} else if (type == Config.HANDLESTREAM_STOP) {
			Stream stream = new Stream();
			stream.setState(StreamState.FINISH);
			out.write(stream);
		}

		session.setAttribute(TYPE_STATE_KEY, (short) 0);
		session.setAttribute(BODYLENGTH_STATE_KEY, 0);
		session.setAttribute(SHORT_HEADER_STATE_KEY, false);
		session.setAttribute(LONG_HEADER_STATE_KEY, false);

		return true;
	}

}
